package main

import (
	"fmt"
	"math/rand"
	"time"

	"github.com/emirpasic/gods/lists/arraylist"
)

type Publisher = func() string
type OnTick = func(state string, lastState string, isFirstTick bool)
type OnError = func(e error)
type OnTimeout = func(state string)

type Stopable interface {
	Start()
	Stop()
}

type Task struct {
	timeout int
	tick    int
	quit    chan bool
	body    func()
}

func (t *Task) Stop() {
	t.quit <- true
}

func (t *Task) Start() {
	for {
		select {
		case <-t.quit:
			return
		default:
			t.body()
			time.Sleep(time.Millisecond * time.Duration(t.tick))
		}
	}
}

func NewTask(body func(), timeout int, tick int) Task {
	return Task{
		body:    body,
		timeout: timeout,
		tick:    tick,
		quit:    make(chan bool, 1),
	}
}

type MakeState struct {
	publisher         Publisher
	onTickCallback    OnTick
	onErrorCallback   OnError
	onTimeoutCallback OnTimeout
	timeoutChecker    *arraylist.List
	lastState         string
	subscribers       map[string]Task

	quit chan bool
}

func NewMachine(publisher Publisher) MakeState {
	return MakeState{
		publisher:      publisher,
		subscribers:    make(map[string]Task),
		timeoutChecker: arraylist.New(),
		quit:           make(chan bool, 1),
	}
}

func (s *MakeState) errorHandle(e error) {
	s.Stop()
	if s.onErrorCallback != nil {
		s.onErrorCallback(e)
	} else {
		panic(e)
	}
}

func (s *MakeState) timeoutHandle(state string) {
	s.Stop()
	if s.onTimeoutCallback != nil {
		s.onTimeoutCallback(state)
	} else {
		panic(fmt.Errorf("state: %s超时", state))
	}
}

func (s *MakeState) startTimeoutChecker(state string, ms int) {
	if ms > 0 {
		tick := time.After(time.Millisecond * time.Duration(ms))
		quit := make(chan bool, 1)
		s.timeoutChecker.Add(quit)
		for {
			select {
			case <-tick:
				fmt.Print("tick!!!!!")
				s.timeoutHandle(state)
			case <-quit:
				return
			}
		}
	}
}

func (s *MakeState) stopTimeoutChecker() {
	iterator := s.timeoutChecker.Iterator()
	for iterator.Next() {
		v := iterator.Value().(chan bool)
		if v != nil {
			v <- true
		}
	}
	s.timeoutChecker.Clear()
}

func (s *MakeState) publish(isFirstTick bool) {
	state := s.publisher()
	if s.onTickCallback != nil {
		s.onTickCallback(state, s.lastState, isFirstTick)
	}
	if s.lastState != state {
		s.lastState = state
		s.stopTimeoutChecker()
		if task, exist := s.subscribers[state]; exist {
			go s.startTimeoutChecker(state, task.timeout)
			task.body()
			s.stopTimeoutChecker()
		}
	}
}

func (s *MakeState) On(state string, subsSubscriber func(), timeout int, tick int) *MakeState {
	if _, exist := s.subscribers[state]; !exist {
		s.subscribers[state] = NewTask(subsSubscriber, timeout, tick)
	} else {
		panic(fmt.Errorf("on state:[%s] already exists", state))
	}
	return s
}

func (s *MakeState) OnTick(cb OnTick) *MakeState {
	s.onTickCallback = cb
	return s
}

func (s *MakeState) OnTimeout(cb OnTimeout) *MakeState {
	s.onTimeoutCallback = cb
	return s
}

func (s *MakeState) OnError(cb OnError) *MakeState {
	s.onErrorCallback = cb
	return s
}

func (s *MakeState) Start() {
	go func() {
		defer func() {
			if err := recover(); err != nil {
				s.errorHandle(err.(error))
			}
		}()
		isFirstTick := true
		for {
			select {
			case <-s.quit:
				fmt.Print("stoped\n")
				return
			default:
				s.publish(isFirstTick)
				isFirstTick = false
				time.Sleep(time.Second)
			}
		}
	}()
}

func (s *MakeState) Stop() {
	s.quit <- true
	s.stopTimeoutChecker()
}

func main() {
	s := NewMachine(func() string {
		h := rand.Intn(10)
		if h >= 8 {
			return "state"
		} else if h >= 5 && h < 8 {
			return "state1"
		} else if h >= 1 && h < 5 {
			return "state2"
		} else {
			return "state3"
		}
	})

	s.On("state", func() {
		time.Sleep(time.Second)
		fmt.Printf("state handle: %s\n", "state")
	}, 200, 0).On("state1", func() {
		time.Sleep(time.Second * 5)
		fmt.Printf("state1 handle: %s\n", "state1")
	}, 200, 0).On("state2", func() {
		time.Sleep(time.Millisecond)
		fmt.Printf("state2 handle: %s\n", "state2")
	}, 0, 0).On("state3", func() {
		panic(fmt.Errorf("state1 errror"))
		fmt.Printf("state3 handle: %s\n", "state3")
	}, 200, 0).OnError(func(e error) {
		fmt.Print(e)
	}).OnTimeout(func(state string) {
		fmt.Printf("timeout: %s\n", state)
	})

	s.Start()

	if s.onErrorCallback == nil {
		fmt.Print("is nil")
	}
	time.Sleep(time.Hour)
}
